<!-- 右键菜单 -->
<template>
  <div v-if="slots.default" ref="slotRef" class="w-full h-full">
    <slot></slot>
  </div>

  <Teleport to="body">
    <transition :name="animation">
      <div
        v-show="config.show"
        ref="contextMenuRef"
        :style="`top: ${config.y}px; left: ${config.x}px`"
        class="absolute z-10 rounded shadow shadow-gray-400"
        :class="{
          'bg-white': theme == 'light',
          'bg-gray-700': theme == 'dark',
        }"
        @contextmenu="
          (e) => {
            e.preventDefault()
            e.stopPropagation()
            close()
          }
        "
      >
        <div
          v-for="(item, index) in menu"
          v-show="item.show == undefined ? true : item.show"
          :key="index"
          class="relative"
        >
          <ContextMenuItem
            :data="data"
            :menu="menu"
            :one="item"
            :rightSpace="config.rightSpace"
            :IsIcon="IsIcon"
            :theme="theme"
            @select="(e: any) => emit('select', e)"
          >
            <template v-for="items in slotList()" #[items]="{ one, item }">
              <slot
                :name="items"
                :menu="menu"
                :data="data"
                :item="item"
                :one="one"
              ></slot>
            </template>
          </ContextMenuItem>
        </div>
      </div>
    </transition>
  </Teleport>
</template>

<script setup lang="ts" name="ContextMenu">
import {
  onMounted,
  onUnmounted,
  reactive,
  ref,
  nextTick,
  getCurrentInstance,
} from 'vue'

import ContextMenuItem from './ContextMenuItem.vue'

const emit = defineEmits(['open', 'close', 'select'])

const props: any = defineProps({
  // 元素
  dom: Object,
  // 元素id 先去找dom,没有才找id
  id: {
    type: String,
    default: 'ContextMenu',
  },
  // 数据
  data: [String, Number, Boolean, Array, Object],
  // 菜单
  menu: {
    type: Array as any,
    default: () => {
      return [
        { key: 1, name: '菜单1', icon: '' },
        {
          key: 2,
          name: '菜单222222222',
          borderBottom: true,
          fn: () => {},
          children: [
            { key: 21, name: '菜单2-1' },
            {
              key: 22,
              name: '菜单2-2',
              borderBottom: true,
              children: [
                { key: 221, name: '菜单2-2-1' },
                {
                  key: 222,
                  name: '菜单2-2-2',
                  borderBottom: true,
                  children: [
                    { key: 2221, name: '菜单2-2-2-1' },
                    { key: 2222, name: '菜单2-2-2-2', borderBottom: true },
                    { key: 2223, name: '菜单2-2-2-3' },
                  ],
                },
                { key: 223, name: '菜单2-2-3' },
                { key: 224, name: '菜单2-2-4' },
              ],
            },
            { key: 23, name: '菜单2-3' },
          ],
        },

        { key: 3, name: '菜单3' },
      ]
    },
  },
  // 动画  el-fade-in-linear/el-fade-in/el-zoom-in-center/el-zoom-in-top/el-zoom-in-bottom/el-collapse-transition
  animation: {
    type: [String, Boolean] as any,
    default: 'el-zoom-in-top',
  },
  // 主题
  theme: {
    type: String,
    default: 'light', // light/dark
  },
  // 是否 含有图标
  IsIcon: Boolean,
})

// config配置
const config = reactive({
  show: false,
  x: 0,
  y: 0,
  rightSpace: 0, // 距离右侧空白距离
})
const { slots }: any = getCurrentInstance() // 插槽
const slotRef = ref() // 插槽元素
const contextMenuRef = ref() // 当前元素
let element: any = null // 需要右键菜单的元素

// 打开
function open(e: any) {
  e.preventDefault()
  e.stopPropagation()
  config.show = true

  nextTick(() => {
    let { clientWidth, clientHeight } = document.documentElement // 屏幕 宽度/高度
    let { offsetWidth, offsetHeight } = contextMenuRef.value // 当前元素 宽度/高度
    let { clientX, clientY } = e // 鼠标  左侧/顶部距离

    config.x = clientX
    config.y = clientY
    config.rightSpace = clientWidth - clientX - offsetWidth

    if (clientWidth < clientX + offsetWidth + 10) {
      config.x = clientX - offsetWidth
    }
    if (clientHeight < clientY + offsetHeight + 10) {
      config.y = clientY - offsetHeight
    }

    emit('open')
  })
}
// 关闭
function close() {
  config.show = false
  emit('close')
}
onMounted(() => {
  nextTick(() => {
    if (slotRef.value) element = slotRef.value
    else if (props?.dom?.value) element = props?.dom?.value
    else if (props.dom) element = props.dom
    else if (document.getElementById(props.id)) {
      element = document.getElementById(props.id)
    } else {
      element = window
    }

    element.addEventListener('contextmenu', open, true)
    window.addEventListener('click', close, true)
    window.addEventListener('scroll', close, true)
    if (slotRef.value) window.addEventListener('contextmenu', close, true)
  })
})
onUnmounted(() => {
  element.removeEventListener('contextmenu', open, true)
  window.removeEventListener('click', close, true)
  window.removeEventListener('scroll', close, true)
  if (slotRef.value) window.removeEventListener('contextmenu', close, true)
})

// slot多级嵌套
function slotList() {
  const { menu } = props
  let result: any = []

  let slotCompute = ({ data = [] }) => {
    data.forEach((item) => {
      let { slot, children } = item
      if (slot) result.push(slot)
      if (children) slotCompute({ data: children })
    })
  }
  slotCompute({ data: menu })
  result = window.$util.data.uniqueArr({ data: result }) // 去重

  return result
}
</script>

<style lang="scss" scoped></style>
